﻿using System;
using System.Collections.ObjectModel;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Think.Problems;
using Think.Problems.Solitaire;
using Think.Test.Problems.Solitaire;

namespace Think.Test.Problems
{
    [TestClass]
    public class TableauTest
    {
        [TestMethod]
        public void ConstructTableau()
        {
            // Execute
            new Tableau();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ConstructTableauWithNullRule()
        {
            // Execute
            new Tableau(a_rule: null);
        }

        [TestMethod]
        public void AddCardFaceDown()
        {
            // Setup
            var tableau = new Tableau();

            // Execute
            tableau.Add("5H", CardSide.FaceDown);

            // Assert
            Assert.AreEqual(1, tableau.Count);
            Assert.AreEqual(1, tableau.FaceDownCount);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddNullCard()
        {
            // Setup
            var tableau = new Tableau();

            // Execute
            tableau.Add(a_card: null, a_side: CardSide.FaceDown);
        }

        [TestMethod]
        public void AddCarFaceUp()
        {
            // Setup
            var tableau = new Tableau();

            // Execute
            tableau.Add("AH", CardSide.FaceUp);

            // Assert
            Assert.AreEqual(1, tableau.Count);
            Assert.AreEqual(1, tableau.FaceUpCount);
        }

        [TestMethod]
        public void AddFaceUpAndFaceDownCards()
        {
            // Setup
            var tableau = new Tableau();

            // Execute
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            tableau.Add("5D", CardSide.FaceUp);
            tableau.Add("4C", CardSide.FaceUp);
            tableau.Add("3H", CardSide.FaceUp);

            // Assert
            Assert.AreEqual(5, tableau.Count);
            Assert.AreEqual(2, tableau.FaceDownCount);
            Assert.AreEqual(3, tableau.FaceUpCount);
        }

        [TestMethod]
        public void CheckTopCardOnEmptyTableau()
        {
            // Setup
            var tableau = new Tableau();

            // Assert
            Assert.IsNull(tableau.TopCard);
        }

        [TestMethod]
        public void CheckTopCardTableauWithNoFaceUpCards()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);

            // Assert
            Assert.IsNull(tableau.TopCard);
        }

        [TestMethod]
        public void CheckTopCardTableauWithFaceUpCards()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            tableau.Add("5D", CardSide.FaceUp);
            tableau.Add("4C", CardSide.FaceUp);
            tableau.Add("3H", CardSide.FaceUp);

            // Assert
            Assert.AreEqual("3H", tableau.TopCard);
        }

        [TestMethod]
        public void CheckBaseCardWithFaceDownCards()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            tableau.Add("5D", CardSide.FaceUp);
            tableau.Add("4C", CardSide.FaceUp);
            tableau.Add("3H", CardSide.FaceUp);

            // Assert
            Assert.IsNull(tableau.BottomCard);
        }

        [TestMethod]
        public void CheckBaseCardOnEmptyTableau()
        {
            // Setup
            var tableau = new Tableau();

            // Assert
            Assert.IsNull(tableau.BottomCard);
        }

        [TestMethod]
        public void CheckBaseCardOnTableauWithoutFaceDown()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("5D", CardSide.FaceUp);
            tableau.Add("4C", CardSide.FaceUp);
            tableau.Add("3H", CardSide.FaceUp);

            // Assert
            Assert.AreEqual("5D", tableau.BottomCard);
        }

        [TestMethod]
        public void CheckCanFlipWithEmptyTableau()
        {
            // Setup
            var tableau = new Tableau();

            // Assert
            Assert.IsFalse(tableau.CanFlip);
        }

        [TestMethod]
        public void CheckCanFlipWithTableauThatHasFaceUpCards()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            tableau.Add("5D", CardSide.FaceUp);
            tableau.Add("4C", CardSide.FaceUp);
            tableau.Add("3H", CardSide.FaceUp);

            // Assert
            Assert.IsFalse(tableau.CanFlip);
        }

        [TestMethod]
        public void CheckCanFlipWithTableauThatHasOnlyFaceDown()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            
            // Assert
            Assert.IsTrue(tableau.CanFlip);
        }

        [TestMethod]
        public void FlipTableauThatHasOnlyFaceDown()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);

            // Execute
            tableau.Flip();

            // Assert
            Assert.AreEqual(1, tableau.FaceDownCount);
            Assert.AreEqual(1, tableau.FaceUpCount);
        }

        [TestMethod]
        public void FlipTableauThatHasFaceUpCards()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            tableau.Add("5D", CardSide.FaceUp);

            // Execute
            tableau.Flip();

            // Assert - No Change
            Assert.AreEqual(2, tableau.FaceDownCount);
            Assert.AreEqual(1, tableau.FaceUpCount);
        }

        [TestMethod]
        public void SplitTableau()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            tableau.Add("5D", CardSide.FaceUp);
            tableau.Add("4C", CardSide.FaceUp);
            tableau.Add("3H", CardSide.FaceUp);

            // Execute
            var result = tableau.Split(1);

            // Assert
            Assert.AreEqual(1, tableau.FaceUpCount);
            Assert.AreEqual(2, tableau.FaceDownCount);
            Assert.AreEqual(2, result.FaceUpCount);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void SplitEmptyTableau()
        {
            // Setup
            var tableau = new Tableau();

            // Execute
            var result = tableau.Split(0);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void SplitAtOutOfRangeBaseCard()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("AH", CardSide.FaceDown);
            tableau.Add("AD", CardSide.FaceDown);
            tableau.Add("5D", CardSide.FaceUp);
            tableau.Add("4C", CardSide.FaceUp);
            tableau.Add("3H", CardSide.FaceUp);

            // Execute
            var result = tableau.Split(3);
        }

        [TestMethod]
        public void AddCardFromDeckFaceDown()
        {
            // Setup
            var deck = new Deck();
            var tableau = new Tableau();

            // Execute
            tableau.Add(deck, CardSide.FaceDown);

            // Assert
            Assert.AreEqual(51, deck.Count);
            Assert.AreEqual(1, tableau.FaceDownCount);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddCardFromNullDeck()
        {
            // Setup
            var tableau = new Tableau();

            // Execute
            tableau.Add(a_deck: null, a_side: CardSide.FaceDown);
        }

        [TestMethod]
        public void AddCardFromDeckFaceUp()
        {
            // Setup
            var deck = new FakeDeck();
            var tableau = new Tableau();

            // Execute
            tableau.Add(deck, CardSide.FaceUp);

            // Assert
            Assert.AreEqual(51, deck.Count);
            Assert.AreEqual(1, tableau.FaceUpCount);
        }

        [TestMethod]
        public void AddCardsFromDeckWithSizeLimit()
        {
            // Setup
            var deck = new FakeDeck();
            var tableau = new Tableau();

            // Execute
            tableau.Add(deck, 3);
            tableau.Add(deck, 3);
            tableau.Add(deck, 3);
            tableau.Add(deck, 3);

            // Assert
            Assert.AreEqual(49, deck.Count);
            Assert.AreEqual(3, deck.CalledDraw);
            Assert.AreEqual(2, tableau.FaceDownCount);
            Assert.AreEqual(1, tableau.FaceUpCount);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddCardsFromNullDeckWithSizeLimit()
        {
            // Setup
            var tableau = new Tableau();

            // Execute
            tableau.Add(a_deck:null, a_limit:3);
        }

        [TestMethod]
        public void CheckCanSetWithDefaultRule()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("7C", CardSide.FaceUp);
            tableau1.Add("8D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("9D", CardSide.FaceUp);
            tableau2.Add("10S", CardSide.FaceUp);

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CheckCanSetWithSolitaireRule()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            tableau1.Add("10S", CardSide.FaceUp);
            tableau1.Add("9D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("8C", CardSide.FaceUp);
            tableau2.Add("7H", CardSide.FaceUp);

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CheckCanSetWithSolitairRuleAndTableausThatDefyThatRule()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            tableau1.Add("10S", CardSide.FaceUp);
            tableau1.Add("9D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("8D", CardSide.FaceUp);
            tableau2.Add("7C", CardSide.FaceUp);

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void CheckCanSetWhereTravellingTableauHasFaceDownCards()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceUp);
            tableau1.Add("9D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("8C", CardSide.FaceDown);
            tableau2.Add("7H", CardSide.FaceUp);

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void CheckCanSetWhereBaseTableauHasAllFaceDownCards()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceDown);
            tableau1.Add("9D", CardSide.FaceDown);
            var tableau2 = new Tableau();
            tableau2.Add("8C", CardSide.FaceUp);
            tableau2.Add("7H", CardSide.FaceUp);

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void CheckCanSetWithSolitaireRuleWhereBaseTableauIsEmpty()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            var tableau2 = new Tableau();
            tableau2.Add("KD", CardSide.FaceUp);
            tableau2.Add("QH", CardSide.FaceUp);

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsTrue(result);       
        }

        [TestMethod]
        public void CheckCanSetWithSolitaireRuleWherBaseTableauIsEmptyAndTravellingTableauDoesNotStartWithKing()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            var tableau2 = new Tableau();
            tableau2.Add("3C", CardSide.FaceUp);

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void CheckCanSetWhereTravellingTableauIsEmpty()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceDown);
            tableau1.Add("9D", CardSide.FaceDown);
            var tableau2 = new Tableau();

            // Execute
            var result = tableau1.CanSet(tableau2);

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CheckCanSetWithNullTableau()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceDown);
            tableau1.Add("9D", CardSide.FaceDown);

            // Execute
            tableau1.CanSet(a_travelling: null);
        }
















        [TestMethod]
        public void CheckCanSetCardWithDefaultRule()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("7C", CardSide.FaceUp);
            tableau.Add("8D", CardSide.FaceUp);

            // Execute
            var result = tableau.CanSet("9D");

            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CheckCanSetCardWithSolitaireRule()
        {
            // Setup
            var tableau = new Tableau(new SolitaireRule());
            tableau.Add("10S", CardSide.FaceUp);
            tableau.Add("9D", CardSide.FaceUp);

            // Execute
            var result = tableau.CanSet("8C");

            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CheckCanSetCardWithSolitairRuleAndTableausThatDefyThatRule()
        {
            // Setup
            var tableau = new Tableau(new SolitaireRule());
            tableau.Add("10S", CardSide.FaceUp);
            tableau.Add("9D", CardSide.FaceUp);

            // Execute
            var result = tableau.CanSet("8D");

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void CheckCanSetCardWhereBaseTableauHasAllFaceDownCards()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("10S", CardSide.FaceDown);
            tableau.Add("9D", CardSide.FaceDown);

            // Execute
            var result = tableau.CanSet("8C");

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        public void CheckCanSetCardWithSolitaireRuleWhereBaseTableauIsEmpty()
        {
            // Setup
            var tableau = new Tableau(new SolitaireRule());

            // Execute
            var result = tableau.CanSet("KD");

            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CheckCanSetCardWithSolitaireRuleWherBaseTableauIsEmptyAndCardDoesNotStartWithKing()
        {
            // Setup
            var tableau = new Tableau(new SolitaireRule());

            // Execute
            var result = tableau.CanSet("3C");

            // Assert
            Assert.IsFalse(result);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CheckCanSetWithNullCard()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("10S", CardSide.FaceDown);
            tableau.Add("9D", CardSide.FaceDown);

            // Execute
            tableau.CanSet(a_card: null);
        }

        [TestMethod]
        public void CheckCanSetCardWithInvalidCard()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("10S", CardSide.FaceDown);
            tableau.Add("9D", CardSide.FaceDown);

            // Execute
            var result = tableau.CanSet("1D");

            // Assert
            Assert.IsFalse(result);
        }









        
        
        
        
        
        
        
        
        
        [TestMethod]
        public void SetWithDefaultRule()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("7C", CardSide.FaceUp);
            tableau1.Add("8D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("9D", CardSide.FaceUp);
            tableau2.Add("10S", CardSide.FaceUp);

            // Execute
            tableau1.Set(tableau2);

            // Assert
            Assert.AreEqual(4, tableau1.FaceUpCount);
            CollectionAssert.AreEqual(new [] { "7C", "8D", "9D", "10S" }, tableau1.FaceUp);
        }

        [TestMethod]
        public void SetWithSolitaireRule()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            tableau1.Add("10S", CardSide.FaceUp);
            tableau1.Add("9D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("8C", CardSide.FaceUp);
            tableau2.Add("7H", CardSide.FaceUp);

            // Execute
            tableau1.Set(tableau2);

            // Assert
            Assert.AreEqual(4, tableau1.FaceUpCount);
            CollectionAssert.AreEqual(new[] { "10S", "9D", "8C", "7H" }, tableau1.FaceUp);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void SetWithSolitairRuleAndTableausThatDefyThatRule()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            tableau1.Add("10S", CardSide.FaceUp);
            tableau1.Add("9D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("8D", CardSide.FaceUp);
            tableau2.Add("7C", CardSide.FaceUp);

            // Execute
            tableau1.Set(tableau2);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void SetWhereTravellingTableauHasFaceDownCards()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceUp);
            tableau1.Add("9D", CardSide.FaceUp);
            var tableau2 = new Tableau();
            tableau2.Add("8C", CardSide.FaceDown);
            tableau2.Add("7H", CardSide.FaceUp);

            // Execute
            tableau1.Set(tableau2);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void SetWhereBaseTableauHasAllFaceDownCards()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceDown);
            tableau1.Add("9D", CardSide.FaceDown);
            var tableau2 = new Tableau();
            tableau2.Add("8C", CardSide.FaceUp);
            tableau2.Add("7H", CardSide.FaceUp);

            // Execute
            tableau1.Set(tableau2);
        }

        [TestMethod]
        public void SetWithSolitaireRuleWhereBaseTableauIsEmpty()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            var tableau2 = new Tableau();
            tableau2.Add("KD", CardSide.FaceUp);
            tableau2.Add("QH", CardSide.FaceUp);

            // Execute
            tableau1.Set(tableau2);

            // Assert
            Assert.AreEqual(2, tableau1.FaceUpCount);
            CollectionAssert.AreEqual(new[] { "KD", "QH" }, tableau1.FaceUp);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void SetWithSolitaireRuleWherBaseTableauIsEmptyAndTravellingTableauDoesNotStartWithKing()
        {
            // Setup
            var tableau1 = new Tableau(new SolitaireRule());
            var tableau2 = new Tableau();
            tableau2.Add("3C", CardSide.FaceUp);

            // Execute
            tableau1.Set(tableau2);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void SetWhereTravellingTableauIsEmpty()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceDown);
            tableau1.Add("9D", CardSide.FaceDown);
            var tableau2 = new Tableau();

            // Execute
            tableau1.Set(tableau2);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SetWithNullTableau()
        {
            // Setup
            var tableau1 = new Tableau();
            tableau1.Add("10S", CardSide.FaceDown);
            tableau1.Add("9D", CardSide.FaceDown);

            // Execute
            tableau1.Set(a_travelling: null);
        }

        [TestMethod]
        public void CloneTableau()
        {
            // Setup
            var tableau = new Tableau();
            tableau.Add("10S", CardSide.FaceDown);
            tableau.Add("9D", CardSide.FaceDown);
            tableau.Add("10C", CardSide.FaceUp);
            tableau.Add("9H", CardSide.FaceUp);
            tableau.Add("8C", CardSide.FaceUp);
            tableau.Add("7D", CardSide.FaceUp);

            // Execute
            Tableau result = tableau.Clone();

            // Assert
            Assert.AreEqual(tableau.FaceDownCount, result.FaceDownCount);
            CollectionAssert.AreEqual(tableau.FaceUp, result.FaceUp);
        }
    }
}
